#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <assert.h>

#include "hdb.h"
#include "hmm.h"

typedef struct hitem hitem_t;
struct hitem
{
    int32_t     next_off; //next item's offset
    uint32_t    hash; //1.speed key compare; 2. easy array's expand
    uint16_t    ksize;
    uint16_t    vsize;
    char        key[0];
    //char      key[ksize-1]
    //char      val[0];
    //char      val[vsize-1]
};

typedef struct hdb_hdr hdb_hdr_t;
struct hdb_hdr
{
    uint32_t size;  //bucket array's size
    uint32_t count; //item's count
    int32_t arr[0]; //bucket array
};

struct hdb
{
    hdb_hdr_t*      hdr;   //active memory's header
    
    hdb_hashfunc_t  f_hash;
    int             expand; //htable is expand,default 0
    hmm_t*          mm;     //memory mgr
};


static uint32_t ht_hashfunc(hdatum_t* datum)
{
    uint32_t hash = 0;
    const unsigned char *key = (const unsigned char *)datum->data;
    const unsigned char *p;
    uint16_t i;
    if (datum->size == -1)
    {
        for (p = key; *p; p++)
        {
            hash = hash * 33 + *p;
        }
        datum->size = p - key;
    }
    else
    {
        for (p = key, i = datum->size; i; i--, p++)
        {
            hash = hash * 33 + *p;
        }
    }

    return hash;
}

#define HTABLE_INIT_SIZE  15

hdb_t* hdb_create(int32_t bnum, void* mem_base, int32_t mem_size)
{
    hdb_t* me = calloc(1, sizeof(*me));
    hdb_hdr_t* hdr = 0;
    uint32_t hdr_size = sizeof(hdb_hdr_t)+bnum*sizeof(int32_t);
    
    if (!me)
    {
        return 0;
    }
    me->mm = hmm_new(mem_base, mem_size);
    if (!me->mm)
    {
        free(me);
        return 0;
    }
    hdr = hmm_malloc(me->mm, hdr_size);
    if (!hdr)
    {
        errno = ENOMEM;
        hmm_delete(me->mm);
        free(me);
        return 0;
    }
    hdr->size = bnum;
    hdr->count = 0;
    me->hdr = hdr;
    me->f_hash = ht_hashfunc;
    return me;
}

hdb_t* hdb_open(void* mem_base)
{
    hdb_t* me = calloc(1, sizeof(*me));
    hdb_hdr_t* hdr = 0;
    
    if (!me)
    {
        return 0;
    }
    
    me->mm = hmm_attach(mem_base);
    if (!me->mm)
    {
        free(me);
        return 0;
    }
    hdr = hmm_first_ptr(me->mm);
    
    //verify the header
    assert(hdr->size != 0);
    
    me->hdr = hdr;
    me->f_hash = ht_hashfunc;
    return me;
}

void hdb_close(hdb_t* me)
{
    hmm_delete(me->mm);
    free(me);
}

void hdb_enable_resize(hdb_t* me, int (*resize)(void* ctx, uint32_t newsize), void* ctx)
{
    assert(me && resize);
    hmm_enable_resize(me->mm, resize, ctx);
}

static inline hitem_t* find_item(hmm_t* mm, hitem_t* head, hitem_t** pprev, uint32_t hash, hdatum_t* key)
{
    hitem_t* item = head;
    if (pprev)
        *pprev = head;
    while (item)
    {
        if ((item->hash == hash)
                && (item->ksize == key->size)
                && (0 == memcmp(key->data, item->key, item->ksize))
           )
        {
            return item;
        }

        //next
        if (pprev)
            *pprev = item;
        item = hmm_ptr(mm, item->next_off);
    }
    return 0;
}

int hdb_add(hdb_t* me, hdatum_t* key, hdatum_t* val)
{
    uint32_t hash = 0;
    hitem_t* item = 0;
    hitem_t* prev = 0;
    int idx = 0;

    assert(me && key && key->size>=-1 && val && val->size>=-1);

    //find
    hash = me->f_hash(key);
    idx = hash % me->hdr->size;
    item = find_item(me->mm, hmm_ptr(me->mm, me->hdr->arr[idx]), &prev, hash, key);
    if (item)
    {
        errno = EEXIST;
        return -1;
    }

    //item alloc
    item = hmm_malloc(me->mm, sizeof(*item) + key->size + val->size);
    if (!item)
        return -1;
    item->hash = hash;
    item->ksize = key->size;
    item->vsize = val->size;
    memcpy(item->key, key->data, key->size);
    memcpy(item->key+item->ksize, val->data, val->size);

    //join the list
    item->next_off = me->hdr->arr[idx];
    me->hdr->arr[idx] = hmm_off(me->mm, item);

    me->hdr->count++;
    return 0;
}

int hdb_set(hdb_t* me, hdatum_t* key, hdatum_t* val)
{
    uint32_t hash = 0;
    hitem_t* item = 0;
    hitem_t* prev = 0;
    int idx = 0;

    assert(me && key && key->size>=-1 && val && val->size>=-1);

    //find
    hash = me->f_hash(key);
    idx = hash % me->hdr->size;
    item = find_item(me->mm, hmm_ptr(me->mm, me->hdr->arr[idx]), &prev, hash, key);
    if (item)
    {
        if (val->size > item->vsize)
        {
            //alloc new mem
            hitem_t* newit = hmm_malloc(me->mm, sizeof(*newit) + item->ksize + val->size);
            if (!newit)
            {
                errno = ENOMEM;
                return -1;
            }
            
            //copy
            memcpy(newit, item, sizeof(*newit) + item->ksize);
            newit->vsize = val->size;
            memcpy(newit->key+newit->ksize, val->data, val->size);
            
            //update prev's next_off           
            if (prev == item)
                me->hdr->arr[idx] = hmm_off(me->mm, newit);
            else
                prev->next_off = hmm_off(me->mm, newit);
            
            //free old
            hmm_free(me->mm, item);
        }
        else
        {
            //update current item : memory block(item->vsize-val->size) is lost!
            item->vsize = val->size;
            memcpy(item->key+item->ksize, val->data, val->size);
        }
        return 0;
    }

    //item alloc
    item = hmm_malloc(me->mm, sizeof(*item) + key->size + val->size);
    if (!item)
        return -1;
    item->hash = hash;
    item->ksize = key->size;
    item->vsize = val->size;
    memcpy(item->key, key->data, key->size);
    memcpy(item->key+item->ksize, val->data, val->size);

    //join the list
    item->next_off = me->hdr->arr[idx];
    me->hdr->arr[idx] = hmm_off(me->mm, item);

    me->hdr->count++;
    return 0;
}

int hdb_get(hdb_t* me, hdatum_t* key, hdatum_t* val)
{
    uint32_t hash = 0;
    hitem_t* item = 0;
    hitem_t* prev = 0;
    int idx = 0;

    assert(me && key && key->size>=-1 && val);

    //find
    hash = me->f_hash(key);
    idx = hash % me->hdr->size;
    item = find_item(me->mm, hmm_ptr(me->mm, me->hdr->arr[idx]), &prev, hash, key);
    if (item)
    {
        val->data = item->key+item->ksize;
        val->size = item->vsize;
        return 0;
    }

    errno = ENOENT;
    return -1;
}

int hdb_rm(hdb_t* me, hdatum_t* key, hdatum_t* val)
{
    uint32_t hash = 0;
    hitem_t* item = 0;
    hitem_t* prev = 0;
    int idx = 0;

    assert(me && key && key->size>=-1);

    //find
    hash = me->f_hash(key);
    idx = hash % me->hdr->size;
    item = find_item(me->mm, hmm_ptr(me->mm, me->hdr->arr[idx]), &prev, hash, key);
    if (item)
    {
        if (val)
        {
            val->data = item->key+item->ksize;
            val->size = item->vsize;
        }
        if (item == prev)
            me->hdr->arr[idx] = -1;
        else
            prev->next_off = item->next_off;
        hmm_free(me->mm, item);
        me->hdr->count--;
        return 0;
    }

    errno = ENOENT;
    return -1;
}

void hdb_set_hash(hdb_t* me, hdb_hashfunc_t func)
{
    assert(me && func);
    me->f_hash = func;
}

void hdb_iterate(hdb_t* me, hdb_iterfunc_t iter, void* ctx)
{
    uint32_t i = 0;
    hdatum_t key;
    hdatum_t val;
    hdb_action_e action;

    assert(me && iter);
    for (i=0; i<me->hdr->size; i++)
    {
        hitem_t* prev = hmm_ptr(me->mm, me->hdr->arr[i]);
        hitem_t* item = prev;
        while (item)
        {
            key.data = item->key;
            key.size = item->ksize;
            val.data = item->key + item->ksize;
            val.size = item->vsize;
            action = iter(ctx, &key, &val);
            if (action & HDB_REMOVE)
            {
                hitem_t* old = item;

                //remove from curpos
                if (prev == item)
                    me->hdr->arr[i] = -1;
                else
                    prev->next_off = item->next_off;
                item = hmm_ptr(me->mm, item->next_off);

                //free it
                hmm_free(me->mm, old);
                me->hdr->count--;
                if (action & HDB_CONTINUE)
                    continue;
                assert(action & HDB_BREAK);
                return;
            }
            else if (action & HDB_BREAK)
            {
                return;
            }
            else
            {
                assert(action & HDB_CONTINUE);
            }

            //next
            prev = item;
            item = hmm_ptr(me->mm, item->next_off);
        }
    }
}

